//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#include "Platforms/Android/APlatform.h"
#include "Platforms/Android/AJavaInterface.h"

#if defined( PLATFORM_ANDROID ) or defined( ANDROID_EMULATOR )

namespace Engine
{
namespace Platforms
{

	AndroidPlatform *	AndroidPlatform::_instance = null;
	
/*
=================================================
	constructor
=================================================
*/
	AndroidPlatform::AndroidPlatform () :
		_events( ESS() ), _quitRenderThread(true), _glesInitialized(false),
		_onThreadStarted( SyncEvent::AUTO_RESET ), _requiredDeviceType( EDevice::AUTO ),
		_msgID(0), _onExitLoop(false), _camera( ESS() )
	{
		if ( _instance == null )
			_instance = this;

		_javaInterface = new AndroidJavaInterface( *this );
		
		ESS()->GetEventSystem()->AttachEventListener(
			DelegateBuilder::Create( AndroidPlatformPtr(this), &AndroidPlatform::_OnEngineOutEvent ),	ESystemEvent::ENGINE_OUT );
	}
		
/*
=================================================
	destructor
=================================================
*/
	AndroidPlatform::~AndroidPlatform ()
	{
		// exit render thread
		if ( _renderThread.IsValid() )
		{
			ParallelOp	op;
			FunctionBuilder::Create( op.func, AndroidPlatformPtr(this), &AndroidPlatform::_ExitRenderLoop );

			PushToRenderThread( op );

			_onExitLoop = true;

			while ( _onExitLoop )
			{
				ProcessMessages();
			}

			_renderThread.Wait();
		}
		
		ESS()->GetEventSystem()->DetachEventListener(
			DelegateBuilder::Create( AndroidPlatformPtr(this), &AndroidPlatform::_OnEngineOutEvent ),	ESystemEvent::ENGINE_OUT );

		if ( _instance == this )
			_instance = null;
	}
	
/*
=================================================
	Init
=================================================
*/
	void AndroidPlatform::Init (int supportedSensors)
	{
		_timer.Start();
		
		CHECK_ERR( g_CreateMainApplication != null, void() );
		CHECK_ERR( _renderThread.Create( &_RenderLoopProc, this ), void() );

		_sensorManager.Init( ESS(), supportedSensors );

		CHECK_ERR( _onThreadStarted.Wait(), void() );
	}
	
/*
=================================================
	EnterForeground
=================================================
*/
	void AndroidPlatform::EnterForeground (bool completed)
	{
		// onResume - completed = true
		// onStart  - completed = false

		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type		 = SysEvent::Application::ENTER_FOREGROUND;
		ev.app.completed = completed;
		
		SendEvent( ev );
		Iterate();
	}
	
/*
=================================================
	EnterBackground
=================================================
*/
	void AndroidPlatform::EnterBackground (bool completed)
	{
		// onPause - completed = false
		// onStop  - completed = true

		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type		 = SysEvent::Application::ENTER_BACKGROUND;
		ev.app.completed = completed;
		
		SendEvent( ev );
		Iterate();
	}
	
/*
=================================================
	SurfaceChanged
=================================================
*/
	void AndroidPlatform::SurfaceChanged (const AndroidWindowSurface &surface, uint width, uint height)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, AndroidPlatformPtr(this), &AndroidPlatform::_OnSurfaceChanged, surface );

		PushToRenderThread( op );

		Resize( width, height );
	}
	
/*
=================================================
	SurfaceDestroyed
=================================================
*/
	void AndroidPlatform::SurfaceDestroyed ()
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, AndroidPlatformPtr(this), &AndroidPlatform::_OnSurfaceDestroyed );

		PushToRenderThread( op );

		Iterate();
	}
	
/*
=================================================
	Resize
=================================================
*/
	void AndroidPlatform::Resize (uint width, uint height)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, AndroidPlatformPtr(this), &AndroidPlatform::_SetDeviceResolution, ivec2( width, height ) );

		PushToRenderThread( op );

		TSysEvent< ESystemEvent::WINDOW >	ev;

		ev.window.type	 = SysEvent::Window::RESIZED;
		ev.window.width	 = width;
		ev.window.height = height;

		SendEvent( ev );
		Iterate();
	}

/*
=================================================
	VisibilityChanged
=================================================
*/
	void AndroidPlatform::VisibilityChanged (bool isVisible)
	{
		TSysEvent< ESystemEvent::WINDOW >	ev;

		ev.window.type = isVisible ? SysEvent::Window::SHOWN : SysEvent::Window::HIDDEN;

		SendEvent( ev );
		Iterate();
	}

/*
=================================================
	LowMemory
=================================================
*/
	void AndroidPlatform::LowMemory ()
	{
		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type = SysEvent::Application::LOW_MEMORY;
		
		SendEvent( ev );
		Iterate();
	}

/*
=================================================
	SetDirectory
=================================================
*/
	void AndroidPlatform::SetDirectory (uni_c_string dir, EDirectory::type type)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, AndroidPlatformPtr(this), &AndroidPlatform::_SetDirectory, dir, type );

		PushToRenderThread( op );

		Iterate();
	}

/*
=================================================
	SetDevice
=================================================
*/
	void AndroidPlatform::SetDevice (Device *dev)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, AndroidPlatformPtr(this), &AndroidPlatform::_SetDevice, dev );

		PushToRenderThread( op );

		Iterate();
	}

/*
=================================================
	Iterate
=================================================
*/
	void AndroidPlatform::Iterate ()
	{
		ASSERT( this->IsCurrent() );
		ParallelThreadManager::Iterate( this, _application.ptr() );
	}
	
/*
=================================================
	IterateInRenderThread
=================================================
*/
	void AndroidPlatform::IterateInRenderThread ()
	{
		ASSERT( _application->IsCurrent() );
		ParallelThreadManager::Iterate( _application.ptr(), this );
	}

/*
=================================================
	InitRender
=================================================
*/
	bool AndroidPlatform::InitRender (INOUT VideoSettings &vs)
	{
		CHECK_ERR( _renderThread.IsCurrent() );

		ContextEGL::Descriptor &	desc = ReferenceCast< ContextEGL::Descriptor >( vs );

		_renderContext.SetCallbacks( &_ContextEGL_InitWindow,
									 &_ContextEGL_ReleaseWindow,
									 &_ContextEGL_OnContextLost,
									 null );

		CHECK_ERR( _renderContext.Initialize( desc ) );
		return true;
	}

/*
=================================================
	SwapBuffers
=================================================
*/
	void AndroidPlatform::SwapBuffers ()
	{
		_renderContext.SwapBuffers();
	}
	
/*
=================================================
	IsRenderingAvailable
=================================================
*/
	bool AndroidPlatform::IsRenderingAvailable ()
	{
		return	_renderContext.IsContextInitialized()	and
				_renderContext.IsSurfaceCreated()		and
				_renderContext.IsContextCurrent();
	}
	
/*
=================================================
	EnableAudio
=================================================
*/
	void AndroidPlatform::EnableAudio (bool enabled)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, GetJavaInterface(), &AndroidJavaInterface::EnableAudio, enabled );

		Push( op );
	}
	
/*
=================================================
	CheckLicense
=================================================
*/
	void AndroidPlatform::CheckLicense (const OnLicenseChecked_t &callback, uni_c_string publicKey)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func,
			GetJavaInterface(), &AndroidJavaInterface::CheckLicense,
			callback, publicKey, GetDevice().GetDeviceID() );

		Push( op );
	}

/*
=================================================
	Terminate
=================================================
*/
	void AndroidPlatform::Terminate ()
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, GetJavaInterface(), &AndroidJavaInterface::CloseActivity );

		Push( op );
	}
	
/*
=================================================
	Vibrate
=================================================
*/
	void AndroidPlatform::Vibrate (const uni_array<const int64> &pattern)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, GetJavaInterface(), &AndroidJavaInterface::Vibrate, pattern );

		Push( op );
	}

/*
=================================================
	InitDevice
=================================================
*/
	void AndroidPlatform::InitDevice (EDevice::type deviceType)
	{
		_requiredDeviceType = deviceType;
	}
	
/*
=================================================
	SetDisplayOrientation
=================================================
*/
	void AndroidPlatform::SetDisplayOrientation (EDisplayOrientation::type orientation)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, GetJavaInterface(), &AndroidJavaInterface::SetDisplayOrientation, orientation );

		Push( op );
	}

/*
=================================================
	BackgroundMode
=================================================
*
	void AndroidPlatform::BackgroundMode (bool enabled)
	{
		Push( ParallelOp::CallbackDataCall( this, &_BackgroundModeCommand, enabled ) );
	}

/*
=================================================
	SendEmail
=================================================
*/
	void AndroidPlatform::SendEmail (uni_c_string address, uni_c_string subject,
									 uni_c_string text, uni_c_string attachment)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func,
			GetJavaInterface(), &AndroidJavaInterface::SendEmail,
			address, subject, text, attachment );

		Push( op );
	}
	
/*
=================================================
	PhoneCall
=================================================
*/
	void AndroidPlatform::OpenURL (uni_c_string url)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, GetJavaInterface(), &AndroidJavaInterface::OpenURL, url );

		Push( op );
	}

/*
=================================================
	ShowNotification
=================================================
*/	
	void AndroidPlatform::ShowNotification (uni_c_string text, bool longTime)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, GetJavaInterface(), &AndroidJavaInterface::Notification, text, longTime );

		Push( op );
	}
	
/*
=================================================
	ShowMessage
=================================================
*/
	void AndroidPlatform::ShowMessage (const OnMessageResult_t &callback,
										uni_c_string caption, uni_c_string text,
										uni_c_string positive, uni_c_string negative)
	{
		int	id = -1;
		
		if ( callback.IsValid() )
		{
			id = ++_msgID;
			_msgCallbacks.Add( id, callback );
		}
		
		ParallelOp	op;
		FunctionBuilder::Create( op.func, GetJavaInterface(), &AndroidJavaInterface::Message, id, caption, text, positive, negative );

		Push( op );
	}
	
/*
=================================================
	OnLicenseChecked
=================================================
*/
	void AndroidPlatform::OnLicenseChecked (const OnLicenseChecked_t &callback, ELicenseCheckResponse::type code, const string &error)
	{
		callback.Call( code, error );
	}

/*
=================================================
	MessageResult
=================================================
*/
	void AndroidPlatform::MessageResult (int messageID, int result)
	{
		if ( messageID == -1 )
			return;
		
		ParallelOp	op;
		FunctionBuilder::Create( op.func, AndroidPlatformPtr(this), &AndroidPlatform::_MessageResult, messageID, result );

		PushToRenderThread( op );
	}
	
/*
=================================================
	AudioFocusChanged
=================================================
*/
	void AndroidPlatform::AudioFocusChanged (SysEvent::Audio::EType state)
	{
		TSysEvent< ESystemEvent::AUDIO >	se;

		se.audio.type	= state;

		SendEvent( se );
	}

/*
=================================================
	SendEvent
=================================================
*/
	inline void AndroidPlatform::SendEvent (const SysEvent &ev)
	{
		_events.Send( ev );
	}
	
/*
=================================================
	SendEventInRenderThread
----
	in render thread
=================================================
*/
	inline void AndroidPlatform::SendEventInRenderThread (const SysEvent &ev)
	{
		ASSERT( _application.IsNotNull() );
		ASSERT( _application->IsCurrent() );
		_application->GetSubSystems()->GetEventSystem()->Send( ev );
	}

/*
=================================================
	PushToRenderThread
=================================================
*/
	inline void AndroidPlatform::PushToRenderThread (const ParallelOp &op)
	{
		_application->Push( op );
	}

	RVALUE_REFERENCE_ONLY(
		inline void AndroidPlatform::PushToRenderThread (ParallelOp &&op)
		{
			_application->Push( op );
		}
	)
/*
=================================================
	_OnSurfaceChanged
=================================================
*/
	void AndroidPlatform::_OnSurfaceChanged (const AndroidWindowSurface &surface)
	{
		if ( _surface.IsValid() )
		{
			_renderContext.DestroySurface();
		}

		if ( not _renderContext.IsContextInitialized() )
			return;

		_surface = surface;

		_renderContext.CreateSurface( _surface.Get() );
		
		if ( not _glesInitialized )
		{
			using namespace gles;

			_glesInitialized = GLES_Init();

			if ( _glesInitialized )
			{
				string	gpu;

				const char	*	vendor		= null,
							*	renderer	= null,
							*	version		= null,
							*	sl_version	= null;

				GL_CALL( vendor		= (const char *) glGetString( GL_VENDOR ) );
				GL_CALL( renderer	= (const char *) glGetString( GL_RENDERER ) );
				GL_CALL( version	= (const char *) glGetString( GL_VERSION ) );
				GL_CALL( sl_version	= (const char *) glGetString( GL_SHADING_LANGUAGE_VERSION ) );
				
				gpu << vendor << ' ' << renderer << ", ver: " << version << ", SL ver: " << sl_version;
				_device.SetGPUName( gpu );
				_device.WriteToLog();
			}
		}
		
		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type	= SysEvent::Application::SURFACE_CREATED;
		SendEventInRenderThread( ev );
	}
	
/*
=================================================
	_OnSurfaceDestroyed
=================================================
*/
	void AndroidPlatform::_OnSurfaceDestroyed ()
	{
		if ( not _renderContext.IsSurfaceCreated() )
		{
			_surface.Destroy();
			return;
		}

		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type	= SysEvent::Application::SURFACE_DESTROYED;
		SendEventInRenderThread( ev );
		
		_renderContext.DestroySurface();
		_surface.Destroy();
	}
	
/*
=================================================
	_RenderLoop
=================================================
*/
	void AndroidPlatform::_RenderLoop ()
	{
		_quitRenderThread = false;
		
		// Init //
		g_CreateMainApplication( this, _application.ref() );

		CHECK_ERR( _application.IsNotNull(), void() );
		
		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type = SysEvent::Application::CREATED;
		SendEventInRenderThread( ev );

		Flush();
		_onThreadStarted.Signal();


		SysEvent::time_t	last_timestamp = _GetTimestamp();

		while ( not _quitRenderThread )
		{
			_application->Flush();
			_application->ProcessMessages();

			// Update
			{
				TSysEvent< ESystemEvent::UPDATE >	ev( _GetTimestamp() );

				ev.update.timeDelta			= (float) (ev.timestamp - last_timestamp).ToDouble().GetSec();
				ev.update.redraw			= false;
				ev.update.renderAvailable	= _glesInitialized and _renderContext.IsSurfaceCreated();

				last_timestamp = ev.timestamp;
		
				// TODO: it fix for win platform
#	ifdef PLATFORM_WINDOWS
				if ( ev.update.renderAvailable )
					_renderContext.MakeCurrent();
#	endif
				SendEventInRenderThread( ev );
			}
		}

		
		// Destroy //
		if ( _application.IsNotNull() )
		{
			_OnSurfaceDestroyed();
			
			if ( _glesInitialized )
			{
				_glesInitialized = false;
				gles::GLES_Delete();
				_renderContext.Destroy();
			}

			TSysEvent< ESystemEvent::APPLICATION >	ev;

			ev.app.type = SysEvent::Application::DESTROY;
			SendEventInRenderThread( ev );

			_application->Destroy();
			_application = null;
		}
		
		if ( _glesInitialized )
		{
			_glesInitialized = false;
			gles::GLES_Delete();
			_renderContext.Destroy();
		}

		ParallelOp	op;
		FunctionBuilder::Create( op.func, AndroidPlatformPtr(this), &AndroidPlatform::_OnExitRenderLoop );

		Push( op );

		Flush();
	}

/*
=================================================
	_RenderLoopProc
=================================================
*/
	void AndroidPlatform::_RenderLoopProc (void *param)
	{
		((AndroidPlatform *)param)->_RenderLoop();
	}
	
/*
=================================================
	_SetDirectory
=================================================
*/
	void AndroidPlatform::_SetDirectory (const string &dir, EDirectory::type dirType)
	{
		_directories[ dirType ] = dir;

		LOG( (string("Directory: \"") << dir << "\" is " << EDirectory::Name( dirType )).cstr(), ELog::INFO );
	}
	
/*
=================================================
	_SetDevice
=================================================
*/
	void AndroidPlatform::_SetDevice (Device *dev)
	{
		_device = *dev;
		_device.WriteToLog();
	}
	
/*
=================================================
	_SetDeviceResolution
=================================================
*/
	void AndroidPlatform::_SetDeviceResolution (const ivec2 &dim)
	{
		_device.GetDisplayRef().SetResolution( dim );
	}
	
/*
=================================================
	_ExitRenderLoop
=================================================
*/
	void AndroidPlatform::_ExitRenderLoop ()
	{
		_quitRenderThread = true;
	}
	
/*
=================================================
	_OnExitRenderLoop
=================================================
*/
	void AndroidPlatform::_OnExitRenderLoop ()
	{
		_onExitLoop = false;
	}
	
/*
=================================================
	_MessageResult
=================================================
*/
	void AndroidPlatform::_MessageResult (int messageID, int result)
	{
		usize	index = -1;

		CHECK_ERR( _msgCallbacks.FindIndex( (uint) messageID, index ), void() );
		
		_msgCallbacks[ index ].second.Call( result );
		_msgCallbacks.EraseFromIndex( index );
	}
	
/*
=================================================
	_OnEngineOutEvent
=================================================
*/
	void AndroidPlatform::_OnEngineOutEvent (const SysEvent &ev)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func,
			GetJavaInterface(), &AndroidJavaInterface::ProcessEngineEvent,
			ev.engine.type, ev.engine.data[0], ev.engine.data[1], ev.engine.data[2] );

		Push( op );
	}
	
/*
=================================================
	UpdateDisplayOrientation
=================================================
*/
	void AndroidPlatform::UpdateDisplayOrientation (Display::EOrientation orientation)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, AndroidPlatformPtr(this), &AndroidPlatform::_UpdateDisplayOrientation, orientation );

		PushToRenderThread( op );
	}
	
/*
=================================================
	_UpdateDisplayOrientation
=================================================
*/
	void AndroidPlatform::_UpdateDisplayOrientation (Display::EOrientation orientation)
	{
		TSysEvent< ESystemEvent::WINDOW >	se;

		se.window.type			= SysEvent::Window::ORIENTATION_CHANGED;
		se.window.orientation	= orientation;

		_device.GetDisplayRef().SetOrientation( orientation );

		SendEventInRenderThread( se );
	}

/*
=================================================
	_ContextEGL_InitWindow
=================================================
*/
	bool AndroidPlatform::_ContextEGL_InitWindow (void *window, int format, void *parameter)
	{
#	ifdef PLATFORM_ANDROID
		using namespace android;
		ANativeWindow_setBuffersGeometry( (ANativeWindow *)window, 0, 0, format );
#	endif	// PLATFORM_ANDROID

		return true;
	}
	
/*
=================================================
	_ContextEGL_ReleaseWindow
=================================================
*/
	bool AndroidPlatform::_ContextEGL_ReleaseWindow (void *window, void *parameter)
	{
		return true;
	}
	
/*
=================================================
	_ContextEGL_OnContextLost
=================================================
*/
	void AndroidPlatform::_ContextEGL_OnContextLost (ContextEGL *context, void *parameter)
	{
	}

/*
=================================================
	CreateInstance
=================================================
*/
	void AndroidPlatform::CreateInstance ()
	{
		if ( _instance == null )
		{
			_instance = new AndroidPlatform();
		}
	}
	
/*
=================================================
	DestroyInstance
=================================================
*/
	void AndroidPlatform::DestroyInstance ()
	{
		if ( _instance != null )
		{
			delete _instance;
			_instance = null;
		}
	}
	

}	// Platforms
}	// Engine

#endif	// PLATFORM_ANDROID or ANDROID_EMULATOR